/*
 * @Author: czy
 * @Date: 2021-07-13 08:06:29
 * @LastEditTime: 2021-07-15 21:07:58
 */
#include "stddef.h"
#include "string.h"

#include "sys/type.h"
#include "sys/system.h"
#include "sys/syscall.h"
#include "sys/container.h"
#include "sys/config.h"

#include "../lib/scheduler.h"
#include "../lib/state.h"

// Sort by pid
Zset new_queue = newZset(PCB);
Zset waiting_queue = newZset(PCB);
// Sort by priority
Zset ready_queue = newZset(PCB);

PCB *current_process;

static int current_process_run_time = 0;

void update_run_time(void)
{
    cli();
    current_process_run_time++;
    sti();
    if (ALGORITHM == RR && current_process_run_time * PROCESS_TIMING_ACCURACY > TIMESLICE)
    {
        // RR
        if (DEBUG == 1 && ALGORITHM == RR)
        {
            printk("Time's up!\n");
        }
        _syscall(0, 0, 0);
    }
}

void context_switch(PCB *current, PCB *next)
{
    current->state = next->state;
    current->heap = next->heap;
    current->pid = next->pid;
    current->priority = next->priority;
    current->last_run_time = next->last_run_time;
    current->total_run_time = next->total_run_time;
    current->running_times = next->running_times;
    LD_CTX(next);
}

static unsigned int get_pid(void)
{
    static unsigned int i = 1;
    return i++;
}

unsigned int null2new(int (*tskBody)(void))
{
    // -> new_queue
    PCB task;
    task.pid = get_pid();
    task.state = NEW;
    task.PC = (unsigned int)tskBody;

    task.total_run_time = 0;
    task.last_run_time = 0;
    task.running_times = 0;
    task_priority(&task);
    allocblock(&(task.heap), &(task.stack));
    unsigned int address = (unsigned int)processEnd;
    memcpy((void *)(task.stack), &address, 4);
    task.stack -= 4;
    new_queue.add(&new_queue, task.pid, task);
    return task.pid;
}

void new2ready(unsigned int pid)
{
    // new_queue -> ready_queue
    PCB task;
    new_queue.remove(&new_queue, pid, &task);
    task.state = READY;
    ready_queue.add(&ready_queue, task.priority, task);
}

void ready2running(void)
{
    // ready_queue ->
    current_process_run_time = 0;
    PCB task;
    ready_queue.pop(&ready_queue, 0, &task);
    // FCFS
    if (DEBUG == 1 && ALGORITHM == FCFS)
    {
        printk("PID: %d\n", task.pid);
    }
    // SJF
    if (DEBUG == 1 && ALGORITHM == SJF)
    {
        printk("Average: %dms, Last: %dms\n", task.running_times > 0 ? task.total_run_time / task.running_times : 0, task.last_run_time);
    }
    context_switch(current_process, &task);
}

void running2ready(void)
{
    // -> ready_queue
    if (ST_CTX(current_process) == 0 && current_process->pid > 1)
    {
        // Update run time
        current_process->last_run_time = current_process_run_time * PROCESS_TIMING_ACCURACY;
        current_process->total_run_time += current_process->last_run_time;
        current_process->running_times++;

        current_process->state = READY;
        task_priority(current_process);
        ready_queue.add(&ready_queue, current_process->priority, *current_process);
    }
}

void running2waiting(unsigned int pid)
{
    // TODO: 
    // -> waiting_queue
    current_process->state = WAITING;
    waiting_queue.add(&waiting_queue, current_process->pid, *current_process);
}

void waiting2ready(unsigned int pid)
{
    // TODO: 
    // waiting_queue -> ready_queue
    PCB task;
    waiting_queue.remove(&waiting_queue, pid, &task);
    task.state = READY;
    task_priority(&task);
    ready_queue.add(&ready_queue, task.priority, task);
}

void running2termination(void)
{
    current_process->state = TERMINATION;
    freeblock(current_process->heap);
}

void ready2termination(unsigned int pid)
{
    // ready_queue ->
    PCB *task;
    int index = -1;
    ready_queue.get(&ready_queue, 0);
    while ((task = (PCB *)(ready_queue.next(&ready_queue))) != NULL)
    {
        index++;
        if (task->pid == pid)
        {
            ready_queue.delete(&ready_queue, index);
            break;
        }
    }
}

void termination2null(PCB *task)
{
    kfree(task);
}

void run_process(PCB *task)
{
    current_process_run_time = 0;
    context_switch(current_process, task);
}